package org.sosie2.service.framework;

import java.security.Key;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

import org.opensaml.SAMLAssertion;
import org.opensaml.SAMLAttribute;
import org.opensaml.SAMLAttributeStatement;
import org.opensaml.SAMLException;
import org.opensaml.SAMLNameIdentifier;
import org.opensaml.SAMLStatement;
import org.opensaml.SAMLSubject;
/**
 * 
 * This class encapsulates a saml annotation, including only ONE saml assertion. 
 * An assertion can contain any number of attribute statements.
 * 
 * @author Gabriela
 * @author oppy
 */
public class SAMLAnnotation {
	private String 			 issuer 	  = "SomeSAMLIssuer";
	private Date 		 	 notBefore    = new Date();
	private Date 			 notOnOrAfter = null;
	private ArrayList 		 conditions   = new ArrayList();
	private ArrayList 		 advice		  = new ArrayList();
	private SAMLSubject 	 subject 	  = null;
	protected SAMLAssertion assertion	  = null; 
	
	private static String 	ASSERTION_NAMESPACE = "http://www.unibw.de/sosie2/annotation/framework";
	private static String 	ATTRIBUTES_NAMESPACE = "http://www.unibw.de/sosie2/annotation/attributes";	
	private static String	ASSERTION_TARGET = "TARGET";
	private static long		DEFAULT_VALIDITY_IN_MS = 60000; // 1 min
	
	
	/**
	 * Constructs a SAML Annotation.
	 * 
	 * @param attributes A list of (attribute name, value) pairs
	 * @param subjectName
	 * @param validityInMs
	 * @param target
	 * @throws Exception
	 */
	public SAMLAnnotation(String[][] attributes, String subjectName, long validityInMs, String target) throws Exception {  	
		this.notOnOrAfter = new Date(new Date().getTime() + validityInMs);
		
		SAMLAttributeStatement statement = createAttributeStatement(attributes, subjectName, target);	
		
		ArrayList<SAMLAttributeStatement> as = new ArrayList<SAMLAttributeStatement>();
		as.add(statement);
		
		this.assertion = new SAMLAssertion(this.issuer, this.notBefore, this.notOnOrAfter, this.conditions, this.advice, as);				
	}
	
	public SAMLAnnotation(String[][] attributes, String subjectName, String target) throws Exception {
		this(attributes, subjectName, DEFAULT_VALIDITY_IN_MS, target);
	}
	
	
	public SAMLAnnotation(SAMLAttribute[] attributes, String subjectName, long validityInMs, String target) throws Exception {  
		this.notOnOrAfter = new Date(new Date().getTime() + validityInMs);
    	
		SAMLAttributeStatement statement = createAttributeStatement(attributes, subjectName, target);	
		
		ArrayList<SAMLAttributeStatement> as = new ArrayList<SAMLAttributeStatement>();
		as.add(statement);
		
		this.assertion = new SAMLAssertion(this.issuer, this.notBefore, this.notOnOrAfter, this.conditions, this.advice, as);
	}
	
	public SAMLAnnotation(SAMLAttribute[] attributes, String subjectName, String target) throws Exception {
		this(attributes, subjectName, DEFAULT_VALIDITY_IN_MS, target);
	}

	
	/**
	 * Constructs a SAML Annotation from an existing assertion (it is assumed that the assertion contains all
	 * necessary annotation fields (including target).
	 * 
	 * @param a
	 */
	public SAMLAnnotation(SAMLAssertion a) {
		this.assertion = a;		
	}
		
	
	
	/**
	 * Creates a new SAML Attribute Statement.
	 * 
	 * @param attributes A list of (attribute name, value) pairs
	 * @param subjectName 
	 * @param target
	 * @return
	 * @throws SAMLException
	 */
	public SAMLAttributeStatement createAttributeStatement(String[][] attributes, String subjectName, String target) throws SAMLException {
		SAMLAttributeStatement as = new SAMLAttributeStatement();
		
		Vector<String> confMethods = new Vector<String>();
	    confMethods.add(SAMLSubject.CONF_SENDER_VOUCHES);
		subject = new SAMLSubject(new SAMLNameIdentifier(subjectName,"",""), confMethods, null, null);
		as.setSubject(subject);
		
		for (int i=0; i<attributes.length; i++) {
			SAMLAttribute a = new SAMLAttribute();
			a.setName(attributes[i][0]);
			a.addValue(attributes[i][1]);			
			a.setNamespace(ATTRIBUTES_NAMESPACE);
			as.addAttribute(a);
		}
		
		// Attach the target attribute
		SAMLAttribute targetAttribute = createTargetAttribute(target);
		as.addAttribute(targetAttribute);
				
		return as;
	}
	
	
	/**
	 * Creates a new SAML Attribute Statement.
	 * 
	 * @param attributes
	 * @param subjectName
	 * @param target
	 * @return
	 * @throws SAMLException
	 */ 
	public SAMLAttributeStatement createAttributeStatement(SAMLAttribute[] attributes, String subjectName, String target) throws SAMLException {
		SAMLAttributeStatement as = new SAMLAttributeStatement();
		
		Vector<String> confMethods = new Vector<String>();
	    confMethods.add(SAMLSubject.CONF_SENDER_VOUCHES);
		subject = new SAMLSubject(new SAMLNameIdentifier(subjectName,"",""), confMethods, null, null);
		as.setSubject(subject);
		
		for (int i=0; i<attributes.length; i++) {
			as.addAttribute(attributes[i]);
		}
		
		// Attach the target attribute
		SAMLAttribute targetAttribute = createTargetAttribute(target);
		as.addAttribute(targetAttribute);
				
		return as;
	}
	
	
	public SAMLAttribute createTargetAttribute(String target) throws SAMLException {		
		
		SAMLAttribute a = new SAMLAttribute();
		
		// attach the name to it
		a.setName(ASSERTION_TARGET);
		a.setNamespace(ASSERTION_NAMESPACE);
		
		// add the target value into the attribute 
		Vector<String> values = new Vector<String>();
		values.add(target);
		a.setValues(values);
		
		return a;
	}
	
	public ArrayList<SAMLAttributeStatement> getAttributeStatements() {
		ArrayList<SAMLAttributeStatement> as = new ArrayList<SAMLAttributeStatement>();
		SAMLAssertion assertion = getAssertion();
		for (Iterator v = assertion.getStatements();v.hasNext();) {
			as.add((SAMLAttributeStatement)v.next());
		}
		
		return as;
	}
	
	public SAMLAssertion makeAssertion(ArrayList<SAMLStatement> stats, String issuer) throws SAMLException {
		Iterator it = stats.iterator();
		ArrayList<SAMLStatement> array = new ArrayList<SAMLStatement>();
		array.add((SAMLStatement)it.next());
		
		SAMLAssertion as = new SAMLAssertion(issuer, this.notBefore, this.notOnOrAfter, this.conditions, this.advice, array);
		for (;it.hasNext();)
			as.addStatement((SAMLStatement)it.next());
		
		return as;
	}
	
	public void addStatement(SAMLAttribute[] attributes, String name) throws SAMLException  {
		SAMLAttributeStatement statement = createAttributeStatement(attributes, name, "");	
		ArrayList<SAMLAttributeStatement> as = new ArrayList<SAMLAttributeStatement>();
		as.add(statement);
		
		SAMLAssertion assertion = new SAMLAssertion(this.issuer, this.notBefore, this.notOnOrAfter, this.conditions, this.advice, as);
		this.assertion = assertion;
		
		return;
	}	
	
	
	public String toString() {
		return this.assertion.toString();
	}

	
	public SAMLAssertion getAssertion() {
		return assertion;
	}

	public void setAssertion(SAMLAssertion assertion) {
		this.assertion = assertion;
	}

	
	public String getIssuer() {
		return issuer;
	}

	public void setIssuer(String issuer) {
		this.issuer = issuer;
	}
	
	
	public String getId() {
		return assertion.getId();
	}
	
	public void setId(String newId) {
		assertion.setId(newId);
	}
	
	
	public String getTarget() {
		String target = null;
		for (Iterator it = assertion.getStatements(); it.hasNext();) {
			Object obj = it.next();
			if (obj instanceof SAMLAttributeStatement) {
				for (Iterator i = ((SAMLAttributeStatement)obj).getAttributes(); i.hasNext();) {
					SAMLAttribute a = (SAMLAttribute)i.next();
					if (a.getName().equals(ASSERTION_TARGET) && a.getNamespace().equals(ASSERTION_NAMESPACE))
						return (String)a.getValues().next();
				}
			}
		}
		return target;
	}
		
	public void setTarget(String newTarget) {
		//TODO: Implementation
	}
	

	/**
	 * Signs the Annotation. For more information about the parameters see SAMLAssertion.sign().
	 * 
	 * @param alg The algorithm to be used in the signature (for example, XMLSignature.ALGO_ID_SIGNATURE_RSA_SHA1). See XMLSignature for more details.
	 * @param k The Key to be used in the signature
	 * @param certs Collection of public key certificates to be embedded in the object
	 * @throws SAMLException
	 */
	public void sign(String alg, Key k, Collection certs) throws SAMLException {
		assertion.sign(alg, k, certs);
	}
	
	
	/**
	 * Verifies that the digital signature attached to the annotation is valid.
	 * 
	 * @throws SAMLException
	 */
	public void verify() throws SAMLException {
		assertion.verify();
	}
}
